﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Text;

namespace MemoryViewControl
{
  public partial class MemorySpace : Component
  {
    public struct ByteInfo
    {
      [Flags]
      public enum Flags : byte
      {
        Invalid = 0x01, // not mapped
        Dirty = 0x02, // not the current value
        Changed = 0x04, // written to since last cleared
        Hidden = 0x08,
        HighlightMask = 0xF0, // highlight
        HighlightShift = 4,
      };

      public ushort bits;
      public Flags flags;
      public byte readHeat;
      public byte writeHeat;
      public byte bankHeat;

      public int HighlightIndex
      {
        get { return ((int)(flags & Flags.HighlightMask)) >> (int)Flags.HighlightShift; }
        set { flags = (Flags)(((int)flags & ~(int)Flags.HighlightMask) | ((value << (int)Flags.HighlightShift) & (int)Flags.HighlightMask)); }
      }

      public ByteInfo(ushort bits, Flags flags)
      {
        this.bits = bits;
        this.flags = flags;
        this.readHeat = 0;
        this.writeHeat = 0;
        this.bankHeat = 0;
      }

      public void Cool(float amount)
      {
        int newWrite = (int)(writeHeat * amount);
        int newRead = (int)(readHeat * amount);
        int newBank = (int)(bankHeat * amount);

        writeHeat = (byte)(newWrite > 0 ? newWrite : 0);
        readHeat = (byte)(newRead > 0 ? newRead : 0);
        bankHeat = (byte)(newBank > 0 ? newBank : 0);
      }

      public static ByteInfo InvalidByte = new ByteInfo(0xFFFF, ByteInfo.Flags.Invalid);
    };

    public struct RangeEntry
    {
      private uint m_address;
      private uint m_length;

      public uint Length { get { return m_length; } }

      public uint Address { get { return m_address; } }

      public RangeEntry(uint address, uint length)
      {
        m_address = address;
        m_length = length;
      }
    };

    public class MemoryAddressIterator : IEnumerator<uint>
    {
      private List<MemorySection> memorySections;
      MemorySection section;
      int memorySectionIndex = 0;
      ulong address = 0;

      public MemoryAddressIterator(List<MemorySection> _memorySections)
      {
        memorySections = _memorySections;
      }

      //public MemoryAddressIterator(List<MemorySection> _memorySections)
      //{
      //  m_memorySections = _memorySections;
      //}

      public uint Current
      {
        get { return (uint)address; }
      }

      object System.Collections.IEnumerator.Current
      {
        get { return Current; }
      }

      public bool MoveNext()
      {
        if (section == null)
        {
          if (memorySections.Count > 0)
          {
            memorySectionIndex = 0;
            section = memorySections[memorySectionIndex];
            address = section.StartAddress;
            return true;
          }
        }
        else
        {
          ++address;
          if (address >= section.EndAddress)
          {
            ++memorySectionIndex;
            if (memorySectionIndex >= memorySections.Count)
            {
              section = memorySections[memorySectionIndex];
              address = section.StartAddress;
              return true;
            }

          }
        }
        return false;
      }

      public void Reset()
      {
        section = null;
        address = 0;
        memorySectionIndex = 0;
      }

      public void Dispose()
      {
      }
    }
    
    public MemorySpace()
    {
      InitializeComponent();
    }

    public MemorySpace(IContainer container)
    {
      container.Add(this);

      InitializeComponent();
    }

    private int m_addressBits = 32;
    private ulong m_addressMax = 0x100000000;
    private ulong m_maxValidAddress = 0x100000000;

    private List<MemorySection> m_memorySections = new List<MemorySection>();

    public event PropertyChangedEventHandler PropertyChanged;
    public event EventHandler MemoryChanged;


    public int AddressBits
    {
      get { return m_addressBits; }
      set { if (m_addressBits != value) { m_addressBits = value; m_addressMax = (ulong)(1 << m_addressBits); OnPropertyChanged("AddressBits"); } }
    }

    public int AddressNibbles { get { return ((m_addressBits + 3) / 4); } }

    public ulong AddressValidMax { get { return m_maxValidAddress; } }

    public ulong AddressMax { get { return m_addressMax; } }

    public List<MemorySection> MemorySections { get { return m_memorySections; } }

    protected void OnPropertyChanged(string propertyName)
    {
      if (PropertyChanged != null)
      {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
      }
    }

    protected void OnMemoryChanged()
    {
      if (MemoryChanged != null)
      {
        MemoryChanged(this, EventArgs.Empty);
      }
    }

    public bool MapRange(uint address, uint size, string name, int bits)
    {
      for (int i = 0; i < m_memorySections.Count; ++i)
      {
        MemorySection section = m_memorySections[i];
        if ((address < section.EndAddress) && (section.address < (address + size)))
        {
          return false;
        }
      }

      m_memorySections.Add(new MemorySection(address, size, MemorySection.Flags.ReadWrite, name, bits));
      m_memorySections.Sort(MemorySection.Compare);

      for (int i = 0; i < (m_memorySections.Count - 1); ++i)
      {
        m_memorySections[i].nextSection = m_memorySections[i + 1];
      }

      m_memorySections[m_memorySections.Count - 1].nextSection = null;

      m_maxValidAddress = m_memorySections[m_memorySections.Count - 1].EndAddress;

      OnPropertyChanged("");

      return true;
    }

    public MemorySection GetMemorySection(uint address)
    {
      for (int i = 0; i < m_memorySections.Count; ++i)
      {
        MemorySection section = m_memorySections[i];
        if ((address >= section.address) && (address < section.EndAddress))
        {
          return section;
        }
      }
      return null;
    }

    public ulong GetNextValidAddress(uint address)
    {
      for (int i = 0; i < m_memorySections.Count; ++i)
      {
        MemorySection section = m_memorySections[i];
        if ((address >= section.address) && (address < section.EndAddress))
        {
          return address;
        }
        else if (address < section.address)
        {
          return section.address;
        }
      }
      return AddressMax;
    }

    public void GetMemorySectionAndNext(uint address, out MemorySection atAddress, out MemorySection next)
    {
      atAddress = null;
      next = null;

      for (int i = 0; i < m_memorySections.Count; ++i)
      {
        MemorySection section = m_memorySections[i];
        if ((address >= section.address) && (address < section.EndAddress))
        {
          atAddress = section;
          next = section.nextSection;
          return;
        }
        else if (address < section.address)
        {
          next = section;
          return;
        }
      }
    }

    public bool UpdateMemory(uint address, byte[] data, bool setChanged)
    {
      return UpdateMemory(address, data, (uint)data.Length, setChanged);
    }

    public bool UpdateMemory(uint address, byte[] data, uint length, bool setChanged)
    {
      bool updated = false;

      foreach (MemorySection section in m_memorySections)
      {
        if ((address < section.EndAddress) && (section.address < (address + length)))
        {
          section.UpdateMemory(address, data, length, setChanged);
          updated = true;
        }
      }

      if (updated)
      {
        OnMemoryChanged();
      }

      return updated;
    }

    public bool UpdateMemory(uint address, ushort[] data, bool setChanged)
    {
      return UpdateMemory(address, data, (uint)data.Length, setChanged);
    }

    public bool UpdateMemory(uint address, ushort[] data, uint length, bool setChanged)
    {
      bool updated = false;

      foreach (MemorySection section in m_memorySections)
      {
        if ((address < section.EndAddress) && (section.address < (address + length)))
        {
          section.UpdateMemory(address, data, length, setChanged);
          updated = true;
        }
      }

      if (updated)
      {
        OnMemoryChanged();
      }

      return updated;
    }

    public bool UpdateRead(uint address, uint length)
    {
      bool updated = false;

      foreach (MemorySection section in m_memorySections)
      {
        if ((address < section.EndAddress) && (section.address < (address + length)))
        {
          section.UpdateRead(address, length);
          updated = true;
        }
      }

      if (updated)
      {
        OnMemoryChanged();
      }

      return updated;
    }

    public bool UpdateBank(uint address, uint length)
    {
      bool updated = false;

      foreach (MemorySection section in m_memorySections)
      {
        if ((address < section.EndAddress) && (section.address < (address + length)))
        {
          section.UpdateBank(address, length);
          updated = true;
        }
      }

      if (updated)
      {
        OnMemoryChanged();
      }

      return updated;
    }

    public bool SetHighlight(uint address, uint length, int highlight)
    {
      bool updated = false;

      foreach (MemorySection section in m_memorySections)
      {
        if ((address < section.EndAddress) && (section.address < (address + length)))
        {
          section.SetHighlight(address, length, highlight);
          updated = true;
        }
      }

      if (updated)
      {
        OnMemoryChanged();
      }

      return updated;
    }

    public void ClearHighlight()
    {
      for (int i = 0; i < m_memorySections.Count; ++i)
      {
        m_memorySections[i].SetHighlight(0);
      }

      OnMemoryChanged();
    }

    public void ClearHidden()
    {
      for (int i = 0; i < m_memorySections.Count; ++i)
      {
        m_memorySections[i].ClearHidden();
      }

      OnMemoryChanged();
    }

    public void ClearChanged()
    {
      for (int i = 0; i < m_memorySections.Count; ++i)
      {
        m_memorySections[i].ClearChanged();
      }

      OnMemoryChanged();
    }

    public void SetDirty()
    {
      for (int i = 0; i < m_memorySections.Count; ++i)
      {
        m_memorySections[i].SetDirty();
      }
      OnMemoryChanged();
    }

    public bool SetHidden(uint address, uint length, bool hidden)
    {
      bool updated = false;

      for (int i = 0; i < m_memorySections.Count; ++i)
      {
        MemorySection section = m_memorySections[i];
        if ((address < section.EndAddress) && (section.address < (address + length)))
        {
          section.SetHidden(address, length, hidden);
          updated = true;
        }
      }

      if (updated)
      {
        OnMemoryChanged();
      }

      return updated;
    }

    public void Cool(float amount)
    {
      for (int i = 0; i < m_memorySections.Count; ++i)
      {
        MemorySection section = m_memorySections[i];
        section.Cool(amount);
      }

      OnMemoryChanged();
    }

    public void SetMemoryDirty(uint address, uint size)
    {
      for (int i = 0; i < m_memorySections.Count; ++i)
      {
        MemorySection section = m_memorySections[i];
        if ((address < section.EndAddress) && (section.address < (address + size)))
        {
          section.SetDirty(address, size);
        }
      }
    }

    public ByteInfo GetByteSlow(uint address)
    {
      for (int i = 0; i < m_memorySections.Count; ++i)
      {
        MemorySection section = m_memorySections[i];
        if ((address >= section.address) && (address < section.EndAddress))
        {
          return section.data[address - section.address];
        }
      }

      return ByteInfo.InvalidByte;
    }

    public void GetBytes(uint startAddress, ref ByteInfo[] bytes)
    {
      uint address = startAddress;

      MemorySection section;
      MemorySection nextSection;

      for (int i = 0; i < bytes.Length; ++i, ++address)
      {
        GetMemorySectionAndNext(address, out section, out nextSection);

        if (section != null)
        {
          while ((address < section.EndAddress) && (i < bytes.Length))
          {
            bytes[i] = section.data[address - section.address];
            ++i;
            ++address;
          }
        }
        else if (nextSection != null)
        {
          while ((address < nextSection.StartAddress) && (i < bytes.Length))
          {
            bytes[i] = ByteInfo.InvalidByte;
            ++i;
            ++address;
          }
        }
        else
        {
          while (i < bytes.Length)
          {
            bytes[i] = ByteInfo.InvalidByte;
            ++i;
            ++address;
          }
        }
      }
    }

    public List<RangeEntry> GetDirtyMemoryRanges(uint startAddress, uint length)
    {
      List<RangeEntry> entries = new List<RangeEntry>();

      if (m_memorySections.Count > 0)
      {
        bool inDirtyRange = false;
        uint firstDirty = 0;

        uint endAddress = startAddress + length;
        uint address = startAddress;

        MemorySection section;
        MemorySection nextSection;
        GetMemorySectionAndNext(address, out section, out nextSection);

        if (section == null) section = nextSection;

        while (address < endAddress)
        {
          if (address >= section.EndAddress)
          {
            section = section.nextSection;
            address = (section != null) ? section.StartAddress : (uint)AddressMax;
          }

          if (address >= AddressMax)
            break;

          ByteInfo bi = GetByteSlow(address);
          bool dirty = ((bi.flags & ByteInfo.Flags.Dirty) != 0);

          if (inDirtyRange && !dirty)
          {
            entries.Add(new RangeEntry(firstDirty, address - firstDirty));
          }
          else if (!inDirtyRange && dirty)
          {
            firstDirty = address;
          }

          inDirtyRange = dirty;
          ++address;
        }

        if (inDirtyRange)
        {
          entries.Add(new RangeEntry(firstDirty, address - firstDirty));
        }
      }

      return entries;
    }

    public List<RangeEntry> FindMemory(ushort[] data, uint startAddress, uint length)
    {
      List<RangeEntry> entries = new List<RangeEntry>();

      uint endAddress = startAddress + length - (uint)data.Length;
      uint address = startAddress;

      ByteInfo[] buffer = new ByteInfo[data.Length];

      MemorySection section = GetMemorySection(address);

      while (address < endAddress)
      {
        if (address >= section.EndAddress)
        {
          section = section.nextSection;
          address = (section != null) ? section.StartAddress : (uint)AddressMax;
        }

        if (address >= AddressMax)
          break;

        bool match = true;

        GetBytes(address, ref buffer);

        for (uint i = 0; i < (uint)data.Length; ++i)
        {
          if (buffer[i].bits != data[i])
          {
            match = false;
            break;
          }
        }

        if (match)
        {
          entries.Add(new RangeEntry(address, (uint)data.Length));
        }

        ++address;
      }

      return entries;
    }
  }


  [DebuggerDisplay("Address = {address}, Size = {size}, Name = {name}")]
  public class MemorySection
  {
    [Flags]
    public enum Flags : byte
    {
      None = 0,
      Read = 0x01,
      Write = 0x02,
      ReadWrite = Read | Write,
      Banking = 0x4,
    };

    private string name;
    public uint address;
    public uint size;
    private int bits;
    public Flags flags;
    public MemorySpace.ByteInfo[] data;

    //public MemorySection previousSection;
    public MemorySection nextSection;

    public uint StartAddress { get { return address; } }
    public ulong EndAddress { get { return address + size; } }
    public uint Length { get { return size; } }
    public string Name { get { return name; } }
    public int ByteWidth { get { return bits; } }

    public int DataMask { get { return (1 << bits) - 1; } }

    public MemorySection()
    {
      this.name = string.Empty;
      this.address = 0;
      this.size = 0;
      this.bits = 0;
      this.flags = 0;

      this.data = new MemorySpace.ByteInfo[0];
    }

    public MemorySection(uint address, uint size, Flags flags, string name, int bits)
    {
      this.address = address;
      this.size = size;
      this.bits = bits;
      this.flags = flags;
      this.name = !string.IsNullOrEmpty(name) ? name : string.Format("0x{0:X8}-0x{1:X8}", StartAddress, EndAddress - 1);

      this.data = new MemorySpace.ByteInfo[size];

      for (int i = 0; i < data.Length; ++i)
      {
        this.data[i].bits = 0xFFFF;
        this.data[i].flags = MemorySpace.ByteInfo.Flags.Dirty;
      }
    }

    public void ClearChanged()
    {
      for (uint i = 0; i < data.Length; ++i)
      {
        data[i].flags &= ~MemorySpace.ByteInfo.Flags.Changed;
      }
    }

    public void SetDirty()
    {
      for (uint i = 0; i < data.Length; ++i)
      {
        data[i].flags |= MemorySpace.ByteInfo.Flags.Dirty;
      }
    }

    public void SetDirty(uint address, uint size)
    {
      uint start = Math.Max(address, this.address) - this.address;
      ulong end = Math.Min(address + size, this.EndAddress) - this.address;

      for (uint i = start; i < end; ++i)
      {
        data[i].flags |= MemorySpace.ByteInfo.Flags.Dirty;
      }
    }

    public void SetHighlight(int highlight)
    {
      for (uint i = 0; i < data.Length; ++i)
      {
        data[i].HighlightIndex = highlight;
      }
    }

    public void SetHighlight(uint address, uint size, int highlight)
    {
      uint start = Math.Max(address, this.address) - this.address;
      ulong end = Math.Min(address + size, this.EndAddress) - this.address;

      for (uint i = start; i < end; ++i)
      {
        data[i].HighlightIndex = highlight;
      }
    }

    public void SetMemory(uint address, byte[] data)
    {
      UpdateMemory(address, data, (uint)data.Length, false);
    }

    public void SetMemory(uint address, byte[] data, uint size)
    {
      UpdateMemory(address, data, size, false);
    }

    public void UpdateMemory(uint address, byte[] data, bool setChanged)
    {
      UpdateMemory(address, data, (uint)data.Length, setChanged);
    }

    public void UpdateMemory(uint address, byte[] data, uint size, bool setChanged)
    {
      uint start = Math.Max(address, this.address);
      uint end = Math.Min(address + size, this.address + this.size);

      if (end > start)
      {
        uint srcOffset = start - address;
        uint dstOffset = start - this.address;
        uint length = end - start;

        for (uint i = 0; i < length; ++i)
        {
          MemorySpace.ByteInfo bi = this.data[i + dstOffset];
          bi.bits = data[i + srcOffset];

          if ((bi.bits & DataMask) != bi.bits)
          {
            throw new ApplicationException("Update data does not fit in the bits of the memory section");
          }

          bool wasInvalid = ((bi.flags & MemorySpace.ByteInfo.Flags.Invalid) != 0);

          bi.flags &= ~MemorySpace.ByteInfo.Flags.Invalid;
          bi.flags &= ~MemorySpace.ByteInfo.Flags.Dirty;

          if (setChanged && !wasInvalid)
          {
            bi.flags |= MemorySpace.ByteInfo.Flags.Changed;
            bi.writeHeat = 0xFF;
          }

          this.data[i + dstOffset] = bi;
        }
      }
    }

    public void UpdateMemory(uint address, ushort[] data, bool setChanged)
    {
      UpdateMemory(address, data, (uint)data.Length, setChanged);
    }

    public void UpdateMemory(uint address, ushort[] data, uint size, bool setChanged)
    {
      uint start = Math.Max(address, this.address);
      uint end = Math.Min(address + size, this.address + this.size);

      if (end > start)
      {
        uint srcOffset = start - address;
        uint dstOffset = start - this.address;
        uint length = end - start;

        for (uint i = 0; i < length; ++i)
        {
          MemorySpace.ByteInfo bi = this.data[i + dstOffset];
          bi.bits = data[i + srcOffset];

          if ((bi.bits & DataMask) != bi.bits)
          {
            throw new ApplicationException("Update data does not fit in the bits of the memory section");
          }

          bool wasInvalid = ((bi.flags & MemorySpace.ByteInfo.Flags.Invalid) != 0);

          bi.flags &= ~MemorySpace.ByteInfo.Flags.Invalid;
          bi.flags &= ~MemorySpace.ByteInfo.Flags.Dirty;

          if (setChanged && !wasInvalid)
          {
            bi.flags |= MemorySpace.ByteInfo.Flags.Changed;
            bi.writeHeat = 0xFF;
          }

          this.data[i + dstOffset] = bi;
        }
      }
    }

    public void Cool(float amount)
    {
      for (uint i = 0; i < data.Length; ++i)
      {
        data[i].Cool(amount);
      }
    }

    public void UpdateRead(uint address, uint size)
    {
      uint start = Math.Max(address, this.address) - this.address;
      uint end = Math.Min(address + size, this.address + this.size) - this.address;

      for (uint i = start; i < end; ++i)
      {
        data[i].readHeat = 0xFF;
      }
    }

    public void UpdateBank(uint address, uint size)
    {
      uint start = Math.Max(address, this.address) - this.address;
      uint end = Math.Min(address + size, this.address + this.size) - this.address;

      for (uint i = start; i < end; ++i)
      {
        data[i].bankHeat = 0xFF;
      }
    }

    public void SetHidden(uint address, uint size, bool hidden)
    {
      uint start = Math.Max(address, this.address) - this.address;
      uint end = Math.Min(address + size, this.address + this.size) - this.address;

      for (uint i = start; i < end; ++i)
      {
        if (hidden)
          data[i].flags |= MemorySpace.ByteInfo.Flags.Hidden;
        else
          data[i].flags &= ~MemorySpace.ByteInfo.Flags.Hidden;
      }
    }

    public void ClearHidden()
    {
      for (uint i = 0; i < data.Length; ++i)
      {
        data[i].flags &= ~MemorySpace.ByteInfo.Flags.Hidden;
      }
    }

    public int CalculateVisibleLines(uint alignment, uint width, bool renderHidden, bool renderInvalid)
    {
      int lines = 0;
      uint alignmentOffset = (address % alignment);
      uint startAddress = address - alignmentOffset;
      uint p = startAddress;

      while (p < StartAddress)
      {
        // add lines?
      }

      while (p < EndAddress)
      {
        uint lineStart = p;
        bool allHidden = true;
        bool allInvalid = true;
        bool skipped = false;
        while ((p < (lineStart + width)) && (p < EndAddress))
        {
          MemorySpace.ByteInfo.Flags flags = data[p - address].flags;
          allHidden = allHidden && ((flags & MemorySpace.ByteInfo.Flags.Hidden) != 0);
          allInvalid = allInvalid && ((flags & MemorySpace.ByteInfo.Flags.Invalid) != 0);

          if (!allHidden && !allInvalid)
          {
            break;
          }
          ++p;
        }

        if ((allHidden && !renderHidden) || (allInvalid && !renderInvalid))
        {
          skipped = true;
        }

        p = lineStart + width;
        if (!skipped)
          ++lines;
      }

      return lines;
    }

    public static int Compare(MemorySection lhs, MemorySection rhs)
    {
      return (int)lhs.address - (int)rhs.address;
    }
  };

}
